package org.teamh.gui.engine;

import java.util.logging.Level;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.teamh.gui.engine.ogg.SoundCache;

import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyInputConsumer;
import de.lessvoid.nifty.renderer.lwjgl.render.LwjglRenderDevice;

/**
 * This class provides the basic window for rendering with openGL. It is enhanced by the Nifty library and support nifty-gui as a hud. 
 * @author richard
 *
 */
public abstract class OpenGLView implements Runnable {
	/**
	 * The initial size of the window. Can be changed. 
	 */
	private int width = 800;
	private int height = 600;

	/**
	 * When running in fullscreen set this to true. 
	 */
	private boolean fullscreen;
	
	/**
	 * When setting running to false, you can close the openGL window
	 */
	private boolean running = false;
	
	/**
	 * This is the current display mode, used by the openGL window. Within java, a display mode is needed due to the window resizement. 
	 */
	private DisplayMode displayMode = null;
	
	/**
	 * The title, which is displayed on top of the window, if not in fullscreen. 
	 */
	private String title;
	
	/**
	 * This expression supports the dragging function of the mouse. 
	 */
	private boolean drag;
	
	/**
	 * For time messurement between two frames. 
	 */
	private long timeElapsed;
	
	/**
	 * Count the frames for any second and displays them afterwards on the screen. 
	 */
	private int frames;
	
	/**
	 * Check for counting to one second to reset the frames. 
	 */
	private long timeSecond;
	
	private GUIRenderer guiRenderer;
	
	/**
	 * Creates a window with specific size, title and fullscreen. 
	 * @param title
	 * @param fullscreen
	 * @param width
	 * @param height
	 */
	public OpenGLView(String title, boolean fullscreen, int width, int height) {
		this(title, fullscreen);
		
		this.width = width;
		this.height = height;
	}
	
	/**
	 * Creates a window with title and fullscreen if needed. If fullscreen is false, the window will have a size of 800 * 600. If fullscreen is true, then the window will have the highest resolution the display supports. 
	 * @param title
	 * @param fullscreen
	 */
	public OpenGLView(String title, boolean fullscreen) {
		this.title = title;
		this.fullscreen = fullscreen;
		
		guiRenderer = new GUIRenderer(this);
		
		if(fullscreen) {
			DisplayMode displaymode = Display.getDesktopDisplayMode();
			setSize(displaymode.getWidth(), displaymode.getHeight());
			this.displayMode = displaymode;
		}
	}
	
	/**
	 * Sets the size of the OpenGL window. Be careful only to use values, your display supports. This must be called before the thread from openGLview is started. 
	 * @param width
	 * @param height
	 */
	public void setSize(int width, int height) {
		this.width = width;
		this.height = height;
	}
	
	/**
	 * This is the central function of the openGL view. It creates the window, initializes the openGL environment and starts the loop. Within the loop, there is input management, render management and time management. When the program is requested to close, this method will clean up the openGL environment. Furthermore this function supports the whole application with the openGL render context. 
	 */
	@Override
	public void run() {
		Keyboard.enableRepeatEvents(true);
		
		running = true;
		
		createWindow(fullscreen);
		
		init();
		
		reshape();
		
		timeElapsed = System.nanoTime();
		timeSecond = System.currentTimeMillis() + 1000;
		
		while(!Display.isCloseRequested() && !guiRenderer.isBroken() && running) {			
			guiRenderer.update();
			
			// Checks, if display size changed. If it change, the reshape method
			// must be called and the scenes needs to be updated.
			if (Display.wasResized()) {
				width = Display.getWidth();
				height = Display.getHeight();

				reshape();
			}
			
			manageInput(null);
			
			long lastTimeElapsed = timeElapsed;
			timeElapsed = System.nanoTime();
			render((System.nanoTime() - lastTimeElapsed) / 1000000f);
			
			guiRenderer.render();
			
			frames++;
			
			if(timeSecond < System.currentTimeMillis()) {
				timeSecond = System.currentTimeMillis() + 1000;
				Display.setTitle(title + " - " + frames + " FPS");
				frames = 0;
			}
			
			Display.update();
			Display.sync(60);
		}
		
		dealloc();
		
		Display.destroy();
		
		SoundCache.get().destroy();
	}
	
	/**
	 * You can call this method, if you want to force this application to be closed. 
	 */
	public void close() {
		running = false;
	}
	
	/**
	 * This method manages the input of the application, stored within the events of LWJGL. 
	 * @param consumer
	 */
	public void manageInput(NiftyInputConsumer consumer) {
		while (Keyboard.next()) {
			boolean state = Keyboard.getEventKeyState();
			int eventKey = Keyboard.getEventKey();
			char eventChar = Keyboard.getEventCharacter();
			boolean forceCharacter = false;
			if (state) {
				keyPressed(eventKey);
				if(eventChar == '@') {
					forceCharacter = true;
				}
			} else {
				keyReleased(eventKey);
			}
			
			guiRenderer.processKeyboardEvent(forceCharacter, eventKey, eventChar, state, consumer);
		}
		
		while(Mouse.next()) {
			boolean state = Mouse.getEventButtonState();
			int x = Mouse.getX();
			int y = Mouse.getY();
			int button = Mouse.getEventButton();
			
			if(state) {
				drag = true;
				mousePressed(x, y, button);
			} else {
				if(Mouse.getEventButton() < 0) {
					if(drag) {
						mouseDragged(x, y, button);
					} else {
						mouseMoved(x, y, button);
					}
				} else {
					drag = false;
					mouseReleased(x, y, button);
				}
			}
			
			int wheel = Mouse.getEventDWheel();
			
			if(wheel != 0) {
				mouseWheelMoved(wheel / 120);
			}
			
			guiRenderer.processMouseEvent(x, y, wheel, button, state, consumer);
		}
	}
	
	/**
	 * This function is creating the openGL window. Therefore the most suitable display mode is selected and then the window is created. 
	 * @param fullscreen
	 */
	private void createWindow(boolean fullscreen) {
		java.util.logging.Logger.getLogger("").setLevel(Level.WARNING);
		
		try {
			Display.setFullscreen(fullscreen);

			if(displayMode == null) {
				DisplayMode[] modes = Display.getAvailableDisplayModes();
				for (int i = 0; i < modes.length; i++) {
					if (modes[i].getWidth() == width
							&& modes[i].getHeight() == height
							&& modes[i].getBitsPerPixel() == Display
									.getDesktopDisplayMode().getBitsPerPixel()) {
						displayMode = modes[i];
					}
				}
			}

			if (displayMode != null) {
				Display.setDisplayMode(displayMode);
				Display.setResizable(true);
				Display.setVSyncEnabled(true);
				Display.setTitle(title);
				Display.create();
			} else {
				System.err.println("Display mode is not supported");
				System.exit(0);
			}

		} catch (LWJGLException e) {
			System.err.println("Error while creating the Display");
			System.exit(0);
		}
	}
	
	/**
	 * 
	 * @return the width of the current display
	 */
	public int getWidth() {
		return width;
	}
	
	/**
	 * 
	 * @return the height of the current display
	 */
	public int getHeight() {
		return height;
	}
	
	/**
	 * This method is setting up a correct nifty environment for LWJGL. 
	 */
	public void initNifty() {
		guiRenderer.init();
	}
	
	/**
	 * 
	 * @return the current LWJGL render device. 
	 */
	public LwjglRenderDevice getLwjglRenderDevice() {
		return guiRenderer.getLwjglRenderDevice();
	}
	
	/**
	 * 
	 * @return the current nifty object. 
	 */
	public Nifty getNifty() {
		return guiRenderer.getNifty();
	}
	
	public void init(){}
	
	public void reshape(){}
	
	public void render(float timeElapsed){}
	
	public void keyPressed(int key){}
	
	public void keyReleased(int key){}
	
	public void mousePressed(int posX, int posY, int button) {}
	
	public void mouseReleased(int posX, int posY, int button) {}
	
	public void mouseMoved(int posX, int posY, int button) {}
	
	public void mouseDragged(int posX, int posY, int button) {}
	
	public void mouseWheelMoved(int movement) {}
	
	public void dealloc() {}
}
